#include <iostream>
#include <string>
#include <algorithm>
#include <memory>
#include <map>

#include <errno.h>
#include <string.h>
#include <curses.h>

#include "socket.hpp"
#include "commons.hpp"
#include "protocol.hpp"
#include "player.hpp"
#include "gamer.hpp"
#include "supergamer.hpp"
#include "rle.hpp"

using std::cout;
using namespace Net;
using namespace Proto;
using namespace Client;
using Server::Player;

// silly, simple, super!

struct CharInfo
{
	CharInfo(int _colorPair = 0, char _ch = 0) :
		colorPair(_colorPair), ch(_ch)
	{}
	
	int colorPair;
	char ch;
};

std::map<unsigned char, CharInfo> symbolsMap;
const int PLAYER_COLOR_PAIR = 16;

bool prepareScreen()
{
	if (!initscr())
		return false;
	cbreak();
	noecho();
	nonl();
	meta(stdscr, TRUE);
	intrflush(stdscr, FALSE);
	keypad(stdscr, TRUE);
	if (has_colors()) {
		start_color();
		// swamp
		init_pair(1, COLOR_BLACK, COLOR_MAGENTA);
		// river
		init_pair(2, COLOR_BLACK, COLOR_BLUE);
		// bridge
		init_pair(3, COLOR_BLACK, COLOR_WHITE);
		// forest
		init_pair(4, COLOR_BLACK, COLOR_GREEN);
		// charge station
		init_pair(5, COLOR_BLACK, COLOR_BLUE);
		// bonus
		init_pair(6, COLOR_BLACK, COLOR_BLUE);
		// enemy
		init_pair(7, COLOR_GREEN, COLOR_RED);
		// player
		init_pair(PLAYER_COLOR_PAIR, COLOR_RED, COLOR_BLACK);
		
	}
	curs_set(0);
	clear();
	refresh();
	
	// set symbols map
	symbolsMap[FIELD_NOT_VISIBLE]		= CharInfo(0, '.');
	symbolsMap[FIELD_EMPTY]				= CharInfo(0, ' ');
	symbolsMap[FIELD_SWAMP]				= CharInfo(1, 'S');
	symbolsMap[FIELD_RIVER]				= CharInfo(2, 'R');
	symbolsMap[FIELD_BRIDGE]			= CharInfo(3, 'B');
	symbolsMap[FIELD_FOREST]			= CharInfo(4, 'F');
	symbolsMap[FIELD_CHARGE_STATION]	= CharInfo(5, '+');
	symbolsMap[FIELD_BONUS]				= CharInfo(6, '#');
	symbolsMap[FIELD_ENEMY]				= CharInfo(7, '@');
	return true;
}

void closeScreen()
{
	curs_set(1);
	clear();
	refresh();
	endwin();
}

bool drawScreen(const unsigned char *field, short px, short py)
{
	for (int y = 0; y != FIELD_ROWS; ++y)
		for (int x = 0; x != FIELD_COLS; ++x) {
			CharInfo &ci = symbolsMap[*field];
			if (!ci.ch)
				return false;
			if (ci.colorPair)
				attron(COLOR_PAIR(ci.colorPair));
			mvaddch(y, x, ci.ch);
			if (ci.colorPair)
				attroff(COLOR_PAIR(ci.colorPair));
			++field;
			if (x == px && y == py) {
				attron(COLOR_PAIR(PLAYER_COLOR_PAIR));
				mvaddch(y, x, '@');
				attroff(COLOR_PAIR(PLAYER_COLOR_PAIR));
			}
		}
	curs_set(0);
	refresh();
	return true;
}

int main(int argc, char **argv)
{
	char nickBuffer[NICK_LENGTH + 1];
	if (argc != 3 && argc != 4 || strlen(argv[1]) > NICK_LENGTH) {
		std::cout << "Usage: " << argv[0] << " nick address [port]\n";
		return 1;
	}

	TCPSocket socket;

	try {
		socket.connect(argv[2], SERVER_PORT);
		// memset, strcpy
		// cannot actually remember arguments :)
		// but i am stl magician %)
		memset(nickBuffer, 0, sizeof(nickBuffer));
		strcpy(nickBuffer, argv[1]);
		socket.send(nickBuffer, NICK_LENGTH);
	}
	catch (const TCPSocket::Exception &e) {
		cout << "Cannot connect to server\n";
		return 1;
	}
	cout << "Connected\n";
	
	std::auto_ptr<AI> ai(new SuperGamer);
	Player player;
	Client2Server::SpeedAdjust sa = {0, 0};
	Client2Server::Fire fire = {0, 0, 0};
	size_t toRecv;
	RLEncoder decoder;
	std::vector<unsigned char> buffer;

	prepareScreen();
	try {
		for (;;) {
			// accept data from server
			int result;
			
			socket.recv(result);
			if (result == RESULT_WINNER)
				break;
			socket.recv(player);
			socket.recv(toRecv);
			if (toRecv > FIELD_COLS*FIELD_ROWS*16) {
				closeScreen();
				cout << "Invalid response from server\n";
				return 1;
			}
			buffer.resize(toRecv);
			socket.recv(&buffer[0], toRecv);
			decoder.reset();
			decoder.charge(&buffer[0], toRecv);
			decoder.commitDecode();
			if (decoder.getLength() != FIELD_COLS*FIELD_ROWS)
				throw RLEncoder::Exception();
			
			// draw the field
			if (!drawScreen(static_cast<const unsigned char*>(decoder.getBuffer()), player.x, player.y)){
				closeScreen();
				cout << "Unsupported field format from server\n";
				return 1;
			}
			
			// invoke ai
			ai->load(static_cast<const unsigned char*>(decoder.getBuffer()), player);
			ai->command(sa, fire);
			
			// send data to server
			socket.send(sa);
			socket.send(fire);
		}
		closeScreen();
		cout << "You win\n";
	}
	catch (const TCPSocket::Exception &e) {
		closeScreen();
		cout << "A problem with network communication\n";
		cout << "\t \"" << e.what() << "\" failed, system reported\n"; 
		cout << "\t \"" << strerror(errno) << "\"\n";
		cout << "Disconnected\n";
		return 1;
	}
	catch (const RLEncoder::Exception &e) {
		closeScreen();
		cout << "Cannot decode data from server\n";
		cout << "Disconnected\n";
		return 1;
	}
	return 0;
}
